/*
 * urlhandler.c
 *
 *  Created on: 08/07/2009
 *      Author: andre
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <time.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>

#include "urlhandler.h"
#include "aatracker.h"
#include "trackerutils.h"
#include "trackerlogger.h"


int decodeUrl(char* encodedUrl, char *decodedString, int encodedUrlLength){

    unsigned int  decodedChars=0, temp=0;

    char *currentChar;
    for (currentChar = encodedUrl; currentChar <( encodedUrl + encodedUrlLength); currentChar++) {

        if (*currentChar != '%'){
            decodedString[decodedChars] = *currentChar;

        }else {
            /*com valores adjacentes e mais de 5 cases, o gcc gera uma jump table ao inves de uma sequencia de ifs ao compilar o switch*/
            switch(*(currentChar+1)) {

                case '0':temp = 0;
                         break;
                case '1':temp = ONE_BITMASK;
                         break;
                case '2':temp = TWO_BITMASK;
                         break;
                case '3':temp = THREE_BITMASK;
                         break;
                case '4':temp = FOUR_BITMASK;
                         break;
                case '5':temp = FIVE_BITMASK;
                         break;
                case '6':temp = SIX_BITMASK;
                         break;
                case '7':temp = SEVEN_BITMASK;
                         break;
                case '8':temp = EIGHT_BITMASK;
                         break;
                case '9':temp = NINE_BITMASK;
                         break;
                case 'a':temp = A_BITMASK;
                         break;
                case 'b':temp = B_BITMASK;
                         break;
                case 'c':temp = C_BITMASK;
                         break;
                case 'd':temp = D_BITMASK;
                         break;
                case 'e':temp = E_BITMASK;
                         break;
                case 'f':temp = F_BITMASK;
                         break;
                default: return INVALID_CHAR_IN_URL_ENCODING;
            }

            switch(*(currentChar+2)) {

                case '0':temp +=0;
                         break;
                case '1':temp +=1;
                         break;
                case '2':temp +=2;
                         break;
                case '3':temp +=3;
                         break;
                case '4':temp +=4;
                         break;
                case '5':temp +=5;
                         break;
                case '6':temp +=6;
                         break;
                case '7':temp +=7;
                         break;
                case '8':temp +=8;
                         break;
                case '9':temp +=9;
                         break;
                case 'a':temp +=10;
                         break;
                case 'b':temp +=11;
                         break;
                case 'c':temp +=12;
                         break;
                case 'd':temp +=13;
                         break;
                case 'e':temp +=14;
                         break;
                case 'f':temp +=15;
                         break;
                default: return INVALID_CHAR_IN_URL_ENCODING;
            }

            decodedString[decodedChars]= temp;
            currentChar+=2;
        }
        decodedChars++;
    }

    return decodedChars;
}

int getAnnounceParameters(char* requestUrl, AnnounceParameters *trackerAnnounce){

    if (requestUrl == NULL ){

            //fprintf (stderr, "erro em %s:getRequestParameters() na linha %d: string de url eh NULL \n",__FILE__, __LINE__);
            logError("getAnnounceParameters()", "string de url eh NULL" , __FILE__, __LINE__, 0 );

        return REQUEST_URL_IS_NULL;
    }

    //inicializando variaveis
    int parameterNameStartIndex=0;
    //comecando a procurar parametros do inicio da string de request
    char *parameterNameStart = requestUrl;
    char *currentChar =requestUrl;
    int numFoundParams=0;
    trackerAnnounce->downloaded=0;
    trackerAnnounce->uploaded=0;
    trackerAnnounce->left=0;

    //parameter separators
    char parameterSeparators = ' ' & '&';

    //procurando onde os parametros comecam

    //1o parametro?
    while( (*currentChar != '?' && currentChar != '\0') ){
        currentChar++;
    }
    //*currentChar='&';
    parameterNameStart=currentChar+1;

    //extraindo parametros

    for ( ; ; currentChar++) {

        //comeco/fim de novo parametro?

         if ( (*currentChar != '&' ) && ((*currentChar ^ ' ') != '\0')){
            continue;
         }

        /* o tamanho do par nome_do_param=valor eh a posicao anterior a atual (pois a atual eh um ?/&) menos o indice de comeco * */
        int paramPairLength=currentChar - parameterNameStart  -1;

        /* se for fazer uma comparacao de strings pra cada parametro, vai ficar uma merda de
         * lento. entao testa a primeira letra, e se bater com a inicial de algum parametro
         * faz um teste com memcmp pra ter certeza que eh o parametro certo
        */

        //info_hash
        switch (*parameterNameStart){

            //se bater, copia o campo pra dentro da struct, pulando os caracteres que compoem o nome
            //do parametro
            //exemplo:pra copiar o info_hash, inicia da posicao parameterNameStart e ignore os primeiros 10
            //chars (tem um = depois do nome do parametro)
            case  'i':
                if (memcmp("info_hash", parameterNameStart, 9) ==0) {

                    int decodedChars = decodeUrl(parameterNameStart+10,trackerAnnounce->info_hash,  paramPairLength-9);

                    if (decodedChars != INFO_HASH_SIZE){
                        logError("getAnnounceParameters()", "info_hash nao tem 20 caracteres" , __FILE__, __LINE__, 0 );  return INFO_HASH_TOO_SMALL;
                    }

                    numFoundParams++;
                }
                break;

            //peer_id
            case  'p':

                if (memcmp("peer_id", parameterNameStart, 7) ==0) {

                    int decodedChars = decodeUrl(parameterNameStart+8,trackerAnnounce->peer_id,  paramPairLength-7);

                    if (decodedChars != PEER_ID_SIZE){
                        logError("getAnnounceParameters()", "peer_id nao tem 20 caracteres" , __FILE__, __LINE__, 0 );  return PEER_ID_TOO_SMALL;
                    }

                    numFoundParams++;

                } else if (memcmp("port", parameterNameStart, 4)==0){
                        int port= convertToInt(parameterNameStart+5);

                        if (port < 1024 || port == INVALID_NUMBER){ logError("getAnnounceParameters()", "a porta escolhida é inválida (abaixo de 1024 ou com chars invalidos)" , __FILE__, __LINE__, 0 );  return PORT_NUMBER_TOO_LOW;}

                        trackerAnnounce->port = port;
                        numFoundParams++;
                }
                break;


            case  'd':

                if (memcmp("downloaded", parameterNameStart, 10) ==0){

                    double downloaded= convertToDouble(parameterNameStart+11);

                    if (downloaded < 0 || downloaded == INVALID_NUMBER ){ logError("getAnnounceParameters()", "o numero de bytes em downloaded é inválido (menor que zero ou com chars invalidos) " , __FILE__, __LINE__, 0 );  return DOWNLOADED_IS_NEGATIVE;}
                    trackerAnnounce->downloaded = downloaded;
                    numFoundParams++;
                }
                break;

        //uploaded
            case  'u':

                if (memcmp("uploaded", parameterNameStart, 8) ==0){

                    double uploaded= convertToDouble(parameterNameStart+9);

                    if (uploaded < 0 || uploaded == INVALID_NUMBER ){ logError("getAnnounceParameters()", "o numero de bytes em uploaded é inválido (menor que zero ou com chars invalidos) " , __FILE__, __LINE__, 0 );  return UPLOADED_IS_NEGATIVE;}

                    trackerAnnounce->uploaded = uploaded;
                    numFoundParams++;
                }
                break;

            //left
            case  'l':

                if (memcmp("left", parameterNameStart, 4) ==0){

                    double left= convertToDouble(parameterNameStart+5);

                    if (left < 0 || left == INVALID_NUMBER){ logError("getAnnounceParameters()", "o numero de bytes em left é menor que 0" , __FILE__, __LINE__, 0 );  return LEFT_IS_NEGATIVE;}

                    trackerAnnounce->left =left;
                    numFoundParams++;
                }
                break;

            //event
            case  'e':

                if (memcmp("event", parameterNameStart, 5) ==0){

                    if (memcmp("started", parameterNameStart+6, 7) ==0) {
                        trackerAnnounce->event = STARTED;
                    }
                    else if (memcmp("stopped", parameterNameStart+6, 7) ==0) {
                         trackerAnnounce->event = STOPPED;
                    }
                    else if (memcmp("completed", parameterNameStart+6, 9) ==0) {
                         trackerAnnounce->event = COMPLETED;
                    }
                    else {trackerAnnounce->event = NOEVENT;}

                    numFoundParams++;
                }
                break;

           //compact
            case  'c':

                if (memcmp("compact", parameterNameStart, 7)==0){

                    if (*(parameterNameStart+8) == '1'){
                        trackerAnnounce->compact =TRUE;
                    }
                    else {trackerAnnounce->compact=FALSE;}

                    numFoundParams++;
                }
                break;

           //numwant
            case  'n':

                if (memcmp("numwant", parameterNameStart, 7) ==0){

                    trackerAnnounce->numwant= convertToInt(parameterNameStart+8);
                    numFoundParams++;
                }
                break;

            //key
            case  'k':

                if (memcmp("key", parameterNameStart, 3) ==0){
                    strncpy(trackerAnnounce->key, parameterNameStart+4, paramPairLength-3);
                    numFoundParams++;
                }
                break;
        }

        //depois de identificar o parametro, poe o parameterNameStart no inicio do proximo

        //parameter name start eh o endereco do comeco do nome do parametro. ex: em info_hash, parameterNameStart eh o endereco de 'i'

         parameterNameStart = currentChar+1;

         if ((*currentChar ^ ' ') == 0){
           break;
        }

    }

    if (numFoundParams < MINIMAL_NUMBER_OF_PARAMETERS){

        if (memcmp(trackerAnnounce->info_hash , EMPTY_ARRAY, INFO_HASH_SIZE) ==0)  { logError("getAnnounceParameters()", "o request nao tem info_hash" , __FILE__, __LINE__, 0 );  return REQUEST_WITHOUT_INFO_HASH;}

        else if (memcmp(trackerAnnounce->peer_id , EMPTY_ARRAY, INFO_HASH_SIZE) ==0) { logError("getAnnounceParameters()", "o request nao tem peer_id" , __FILE__, __LINE__, 0 );   return REQUEST_WITHOUT_PEER_ID;}

        else if (trackerAnnounce->port == UNDEFINED ) { logError("getAnnounceParameters()", "o request nao tem port" , __FILE__, __LINE__, 0 );    return REQUEST_WITHOUT_PORT;}

        else if (trackerAnnounce->downloaded == UNDEFINED ) { logError("getAnnounceParameters()", "o request nao tem campo downloaded " , __FILE__, __LINE__, 0 );    return REQUEST_WITHOUT_DOWNLOADED;}

        else if (trackerAnnounce->uploaded == UNDEFINED ) { logError("getAnnounceParameters()", "o request nao tem campo uploaded" , __FILE__, __LINE__, 0 );    return REQUEST_WITHOUT_UPLOADED;}

        else if (trackerAnnounce->left == UNDEFINED ) { logError("getAnnounceParameters()", "o request nao tem campo left" , __FILE__, __LINE__, 0 );    return REQUEST_WITHOUT_LEFT;}

    }

    return numFoundParams;
}

inline int isAnnounce(char* requestUrl){


    if ( (requestUrl == NULL) || (strstr(requestUrl, "/announce") == NULL) ){
        return FALSE;
    }
    return TRUE;

}

inline int isScrape(char* requestUrl){


    if ( (requestUrl == NULL) || (strstr(requestUrl, "/scrape") == NULL) ){
        return FALSE;
    }
    return TRUE;

}
